當我們想要共享邏輯在兩個 Function 之間時,會提取它成為第三個 Function。
在 React 的世界中,Function Component 和 Hook 二者都是 Function,表示提出第三個共享邏輯的 Custom Hook Function,也是適用的。
一個 Custom Hook 是以 use
為開頭為命名的 JS Function,在它內部也會呼叫其他 Hook。
使用 use
開頭的命名規則,除了可以讓專案後續維護者知道是可以使用的 Hook,還可以讓 Lint 工具自動檢查是否違反 Hook 規則。
首先我們先建立一個 App Component 裡面分別使用二個 Component
// App.jsx
import "./styles.css";
import MyComponentOne from "./components/MyComponentOne";
import MyComponentTwo from "./components/MyComponentTwo";
export default function App() {
return (
<div className="App">
<MyComponentOne className="MyComponentOne" />
<MyComponentTwo className="MyComponentTwo" />
</div>
);
}
// components/MyComponentOne.jsx
import { useEffect, useState } from "react";
export default function MyComponentOne({ className }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const setFromEvent = (e) => {
const parent = e.target.parentElement;
if (parent && parent.id && parent.id === "box1") {
setPosition({ x: e.clientX, y: e.clientY });
}
};
window.addEventListener("mousemove", setFromEvent);
return () => {
window.removeEventListener("mousemove", setFromEvent);
};
}, []);
return (
<div id="box1" className={className}>
<h1>MyComponentOne</h1>
<p>x:{position.x}</p>
<p>y:{position.y}</p>
</div>
);
}
// components/MyComponentTwo.jsx
import { useEffect, useState } from "react";
export default function MyComponentTwo({ className }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const setFromEvent = (e) => {
const parent = e.target.parentElement;
if (parent && parent.id && parent.id === "box2") {
setPosition({ x: e.clientX, y: e.clientY });
}
};
window.addEventListener("mousemove", setFromEvent);
return () => {
window.removeEventListener("mousemove", setFromEvent);
};
}, []);
return (
<div id="box2" className={className}>
<h1>MyComponentTwo</h1>
<p>x:{position.x}</p>
<p>y:{position.y}</p>
</div>
);
}
執行結果:https://codesandbox.io/s/throbbing-flower-qedyvk?file=/src/App.jsx:0-331
可以發現二個元件取得滑鼠位置的邏輯其實可抽取出共用的 Function,接下來就來試看看如何製作成 Custom Hook 吧
useMousePosition
抽取重複邏輯use
做為開頭的命名export const useMousePosition = () => {
// Step 2. 先把 useState 及 useEffect 的邏輯搬運至此
};
export const useMousePosition = (elementId) => {
// Step 3. ? 傳入 elementId ? 用來判斷 setPostition 的變更時機
// Step 2. 先把 useState 及 useEffect 的邏輯搬運至此
};
export const useMousePosition = (elementId) => {
// Step 3. ? 傳入 elementId ? 用來判斷 setPostition 的變更時機
// Step 2. 先把 useState 及 useEffect 的邏輯搬運至此
...
// Step 4. 回傳 postion State
return position;
};
// hooks/useMousePosition.js
import { useEffect, useState } from "react";
export const useMousePosition = (elementId) => {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const setFromEvent = (e) => {
const parent = e.target.parentElement;
if (parent && parent.id && parent.id === elmentId) {
setPosition({ x: e.clientX, y: e.clientY });
}
};
window.addEventListener("mousemove", setFromEvent);
return () => {
window.removeEventListener("mousemove", setFromEvent);
};
}, [elmentId]);
return position;
};
// components/MyComponentOne.jsx
import { useMousePosition } from "../hooks/useMousePosition";
export default function MyComponentOne({ className }) {
const position = useMousePosition("box1");
return (
<div id="box1" className={className}>
<h1>MyComponentOne</h1>
<p>x:{position.x}</p>
<p>y:{position.y}</p>
</div>
);
}
// components/MyComponentTwo.jsx
import { useMousePosition } from "../hooks/useMousePosition";
export default function MyComponentTwo({ className }) {
const position = useMousePosition("box2");
return (
<div id="box2" className={className}>
<h1>MyComponentTwo</h1>
<p>x:{position.x}</p>
<p>y:{position.y}</p>
</div>
);
}
程式碼及執行結果:https://codesandbox.io/s/recursing-shtern-q3tbv4?file=/src/hooks/useMousePosition.js
抽取重複邏輯至 useMousePostion 這個 Custom Hook,可以簡化程式碼如下述
使用自定義的 Hook 時,可以封裝的複雜邏輯,簡化程式碼的操作,變成易於使用的 Hook Function。
每次你使用自定義的 Hook 時,所有內部的 state 和 effect 都是完全獨立的
在此之前,已經介紹了 useState
及 useEffect
這二個官方內建基本的 Hook,接下來會再陸續介紹一些官方內建的功能性 Hook。接下來介紹的 useRef
也是一款很經常在使用的官方 React Hook。
https://zh-hant.reactjs.org/docs/hooks-custom.html